package com.bjsxt.config.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.IRedisManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.DispatcherServlet;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @author:liao
 * @Description:TODO shiro最重要的自动配置类
 *                  用于配置shiro的安全管理器 凭证匹配器等信息
 * @date:2020/11/19
 */
@Configuration
@EnableConfigurationProperties({ShiroProperties.class, RedisProperties.class})
public class ShiroAutoConfiguration {
    private ShiroProperties shiroProperties;
    private RedisProperties redisProperties;

    //shiro过滤器名称
    public static final  String SHIRO_FILTER_NAME="shiroFilter";

    public ShiroAutoConfiguration(ShiroProperties shiroProperties, RedisProperties redisProperties) {
        this.shiroProperties = shiroProperties;
        this.redisProperties = redisProperties;
    }

    /**
     * 创建凭证管理器 用于认证时匹配密码规则
     */
    @Bean
    public HashedCredentialsMatcher getHashedCredentialMatcher(){
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //注入散列算法名
        matcher.setHashAlgorithmName(shiroProperties.getHashAlgorithmName());
        //注入散列次数
        matcher.setHashIterations(shiroProperties.getHashIterations());
        return matcher;
    }

    /**
     * 创建自定义realm并注入凭证管理器
     */
    @Bean
    @ConditionalOnClass(value = {UserRealm.class})
    public UserRealm getUserRealm(HashedCredentialsMatcher matcher){
        UserRealm userRealm = new UserRealm();
        //注入凭证管理器
        userRealm.setCredentialsMatcher(matcher);
        return userRealm;
    }

    /**
     * 创建安全管理器并注入UserRealm
     */
    @Bean
    @ConditionalOnClass(value = {DefaultWebSecurityManager.class})
    public DefaultWebSecurityManager getSecurityManager(DefaultWebSessionManager defaultWebSessionManager,UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //注入realm
        securityManager.setRealm(userRealm);
        //注入继承扩展之后的类TokenWebSessionManager
        securityManager.setSessionManager(defaultWebSessionManager);
        return securityManager;
    }

    /**
     * 配置shiro的过滤器链 并注入安全管理器
     */
    @Bean(value = SHIRO_FILTER_NAME)
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        //重写authc过滤器
        Map<String, Filter> filters = new HashMap<>();
        filters.put("authc",new ShiroLoginFilter());
        bean.setFilters(filters);

        //配置过滤器链
        Map<String,String> map = new HashMap<>();
        //从配置类中获取路径信息
        String[] anonUrls = shiroProperties.getAnonUrls();
        //配置不拦截的路径
        if (anonUrls!=null&&anonUrls.length>0){
            for (String anonUrl : anonUrls) {
                map.put(anonUrl,"anon");
            }
        }

        //从配置类中获取路径信息
        String[] authcUrls = shiroProperties.getAuthcUrls();
        //配置需要认证的路径
        if (authcUrls!=null&&authcUrls.length>0){
            for (String authcUrl : authcUrls) {
                map.put(authcUrl,"authc");
            }
        }
        //注入过滤器链
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }

    /**
     * TODO 该方法中的配置相当于在web.xml中的配置
     * 注册DelegatingFilterProxy代理
     * DelegatingFilterProxy代理的作用为在bean容器中
     * 查找名字为TargetBeanName的bean并把所有的filter操作
     * 委托给该bean
     */
    @Bean
    public FilterRegistrationBean<DelegatingFilterProxy> registDelegatingFilterProxy(){
        //创建注册器
        FilterRegistrationBean<DelegatingFilterProxy> bean = new FilterRegistrationBean<>();
        //创建代理过滤器
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        //设置targetName
        proxy.setTargetBeanName(SHIRO_FILTER_NAME);
        proxy.setTargetFilterLifecycle(true);
        //注入代理过滤器
        bean.setFilter(proxy);
        //配置dispatcherServlet
        Collection<String> servletNames = new ArrayList<>();
        servletNames.add(DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
        bean.setServletNames(servletNames);
        return bean;
    }

    //配置redisManager
    @Bean
    public IRedisManager redisManager(){
        //因为RedisManager要操作redis所以要把Redis的客户端给RedisManager
        RedisManager redisManager = new RedisManager();
        //创建配置类
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        //获取redis连接池
        JedisPool jedisPool = new JedisPool(
                jedisPoolConfig,//配置类
                redisProperties.getHost(),//ip地址
                redisProperties.getPort(),//端口
                5000,//连接超时时间
                redisProperties.getPassword()//密码
        );
        //设置连接池
        redisManager.setJedisPool(jedisPool);
        return redisManager;
    }

    //配置redisSessionDAO 并注入IRedisManager
    @Bean
    public RedisSessionDAO redisSessionDAO(IRedisManager redisManager){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        //注入redisManager
        redisSessionDAO.setRedisManager(redisManager);
        //设置token保存时间 7天
        redisSessionDAO.setExpire(7*24*3600);
        return redisSessionDAO;
    }

    //配置会话管理 并注入RedisSessionDAO
    @Bean
    public DefaultWebSessionManager defaultWebSessionManager(RedisSessionDAO redisSessionDAO){
        DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
        //注入redisSessionDAO
        defaultWebSessionManager.setSessionDAO(redisSessionDAO);
        return defaultWebSessionManager;
    }

    /**
     * 开启shiro的注解(如@RequireRoles,@RequiresPermissions)
     * 需借助SpringAop扫描使用Shiro注解的类，并在必要时进行安全逻辑检查
     * 配置以下两个bean(AuthorizationAttributeSourceAdvise和DefaultAdvisorAutoProxyCreator)即可实现此功能
     */

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        //注入安全管理器
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }
}
